package com.utils.SM;

import org.bouncycastle.asn1.gm.GMNamedCurves;
import org.bouncycastle.asn1.x9.X9ECParameters;
import org.bouncycastle.crypto.engines.SM2Engine;
import org.bouncycastle.crypto.params.ECDomainParameters;
import org.bouncycastle.crypto.params.ECPrivateKeyParameters;
import org.bouncycastle.crypto.params.ECPublicKeyParameters;
import org.bouncycastle.crypto.params.ParametersWithRandom;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPrivateKey;
import org.bouncycastle.jcajce.provider.asymmetric.ec.BCECPublicKey;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
import org.bouncycastle.jce.spec.ECPublicKeySpec;
import org.bouncycastle.math.ec.ECPoint;
import org.bouncycastle.util.encoders.Hex;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.SecureRandom;
import java.security.Security;
import java.security.spec.ECGenParameterSpec;

public class SM2Utils {
    public static final String ALGORITHM_EC = "EC";
    public static final String STD_NAME_SM2P256V1 = "sm2p256v1";

    // 获取一条SM2曲线参数
    private static final X9ECParameters parameters = GMNamedCurves.getByName(STD_NAME_SM2P256V1);
    // 椭圆曲线参数规格
    private static final ECParameterSpec ecParameterSpec = new ECParameterSpec(parameters.getCurve(), parameters.getG(), parameters.getN(), parameters.getH());
    // 构造ECC算法参数，曲线方程、椭圆曲线G点、大整数N
    private static final ECDomainParameters domainParameters = new ECDomainParameters(parameters.getCurve(), parameters.getG(), parameters.getN());

    static {
        if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
            Security.addProvider(new BouncyCastleProvider());
        }
    }

    public static String encrypt(String data, String publicKey) throws Exception {
        return encrypt(data, publicKey, SM2Engine.Mode.C1C3C2);
    }

    public static String encrypt(String data, String publicKey, SM2Engine.Mode mode) throws Exception {
        //提取公钥点
        ECPoint pukPoint = parameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 公钥前面的02或者03表示是压缩公钥，04表示未压缩公钥, 04的时候，可以去掉前面的04
        ECPublicKeyParameters publicKeyParameters = new ECPublicKeyParameters(pukPoint, domainParameters);

        SM2Engine sm2Engine = new SM2Engine(mode);
        // 设置sm2为加密模式
        sm2Engine.init(true, new ParametersWithRandom(publicKeyParameters, new SecureRandom()));

        byte[] in = data.getBytes();
        byte[] arrayOfBytes = sm2Engine.processBlock(in, 0, in.length);
        return Hex.toHexString(arrayOfBytes);
    }

    public static String decrypt(String data, String privateKey) throws Exception {
        return decrypt(data, privateKey, SM2Engine.Mode.C1C3C2);
    }

    public static String decrypt(String data, String privateKeyStr, SM2Engine.Mode mode) throws Exception {
        //将十六进制字符串密文转换为字节数组（需要与加密一致，加密是：加密后的字节数组转换为了十六进制字符串）
        byte[] cipherDataByte = Hex.decode(data);
        //通过私钥对象获取私钥的基本域参数。
        final BCECPrivateKey privateKey = getPrivateKey(privateKeyStr);
        //通过私钥值和私钥钥基本参数创建私钥参数对象
        ECPrivateKeyParameters ecPrivateKeyParameters = new ECPrivateKeyParameters(privateKey.getD(), domainParameters);
        //通过解密模式创建解密引擎并初始化
        SM2Engine sm2Engine = new SM2Engine(mode);
        sm2Engine.init(false, ecPrivateKeyParameters);

        //通过解密引擎对密文字节串进行解密
        byte[] arrayOfBytes = sm2Engine.processBlock(cipherDataByte, 0, cipherDataByte.length);
        //将解密后的字节串转换为utf8字符编码的字符串（需要与明文加密时字符串转换成字节串所指定的字符编码保持一致）

        return new String(arrayOfBytes, StandardCharsets.UTF_8);
    }

    public static SM2KeyPair genSM2KeyPair() throws Exception {
        // 获取椭圆曲线相关生成参数规格
        ECGenParameterSpec genParameterSpec = new ECGenParameterSpec(STD_NAME_SM2P256V1);
        // 获取一个椭圆曲线类型的密钥对生成器
        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(ALGORITHM_EC, BouncyCastleProvider.PROVIDER_NAME);
        // 使用SM2的算法区域初始化密钥生成器
        keyPairGenerator.initialize(genParameterSpec, new SecureRandom());
        // 生成密钥对
        KeyPair keyPair = keyPairGenerator.generateKeyPair();
        BCECPrivateKey privateKey = (BCECPrivateKey) keyPair.getPrivate();
        BCECPublicKey publicKey = (BCECPublicKey) keyPair.getPublic();
        return new SM2KeyPair(privateKey, publicKey);
    }

    static class SM2KeyPair {
        private BCECPrivateKey privateKey;
        private BCECPublicKey publicKey;

        public SM2KeyPair(BCECPrivateKey privateKey, BCECPublicKey publicKey) {
            this.privateKey = privateKey;
            this.publicKey = publicKey;
        }

        public BCECPrivateKey getPrivateKey() {
            return privateKey;
        }

        // 拿到32字节的私钥HEX
        public String getPrivateKeyStr() {
            return privateKey.getD().toString(16);
        }

        public void setPrivateKey(BCECPrivateKey privateKey) {
            this.privateKey = privateKey;
        }

        public BCECPublicKey getPublicKey() {
            return publicKey;
        }

        // true  代表压缩密钥，以02、03开头，长度为33字节
        // false 代表未压缩，以04开头，长度为65字节
        public String getPublicKeyStr() {
            return Hex.toHexString(publicKey.getQ().getEncoded(false));
        }

        public void setPublicKey(BCECPublicKey publicKey) {
            this.publicKey = publicKey;
        }
    }

    public static BCECPrivateKey getPrivateKey(String privateKey) throws Exception {
        // 将私钥HEX字符串转换为X值
        BigInteger bigInteger = new BigInteger(privateKey, 16);
        // 获取椭圆曲线KEY生成器
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_EC, BouncyCastleProvider.PROVIDER_NAME);
        // 将X值转为私钥KEY对象
        return (BCECPrivateKey) keyFactory.generatePrivate(new ECPrivateKeySpec(bigInteger, ecParameterSpec));
    }

    public static BCECPublicKey getPublicKey(String publicKey) throws Exception {
        // 将公钥HEX字符串转换为椭圆曲线对应的点
        ECPoint ecPoint = parameters.getCurve().decodePoint(Hex.decode(publicKey));
        // 获取椭圆曲线KEY生成器
        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM_EC, BouncyCastleProvider.PROVIDER_NAME);
        // 将椭圆曲线点转为公钥KEY对象
        return (BCECPublicKey) keyFactory.generatePublic(new ECPublicKeySpec(ecPoint, ecParameterSpec));
    }

}
